package com.nulldev.util.networking.npacket4.util;

import static com.nulldev.util.data.CompressionAPI.thirdparty.heatshrink.Result.Code.MORE;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import com.nulldev.util.data.Charsets.CharsetUtil;
import com.nulldev.util.data.CompressionAPI.thirdparty.heatshrink.HeatShrinkDecoder;
import com.nulldev.util.data.CompressionAPI.thirdparty.heatshrink.HeatShrinkEncoder;
import com.nulldev.util.data.CompressionAPI.thirdparty.heatshrink.Result;
import com.nulldev.util.data.CompressionAPI.thirdparty.heatshrink.Result.Code;
import com.nulldev.util.io.IOUtils;

public class nPacketUtil {

	/* read */

	public static void __read(final InputStream bais, final byte[] arr) throws IOException {
		IOUtils.readFully(bais, arr);
	}

	public static long __readLong(final InputStream bais) throws IOException {
		final byte[] data = IOUtils.readFully(bais, 0, 8);
		return getInt8(data);
	}

	public static int __readInt(final InputStream bais) throws IOException {
		final byte[] data = IOUtils.readFully(bais, 0, 4);
		return (int) getInt4(data);
	}

	public static short __readShort(final InputStream bais) throws IOException {
		final byte[] data = IOUtils.readFully(bais, 0, 2);
		return (short) getInt2(data);
	}

	public static String __readUTF(final InputStream bais) throws IOException {
		final int len = __readInt(bais);
		final byte[] str = new byte[len];
		IOUtils.readFully(bais, str);
		return new String(str, CharsetUtil.UTF_8);
	}

	public static String __readASCII(final InputStream bais) throws IOException {
		final int len = __readInt(bais);
		final byte[] str = new byte[len];
		IOUtils.readFully(bais, str);
		return new String(str, CharsetUtil.US_ASCII);
	}

	/* write */

	public static void __writeLong(final OutputStream baos, final long value) throws IOException {
		writeInt8(baos, value);
	}

	public static void __writeInt(final OutputStream baos, final int integer) throws IOException {
		writeInt4(baos, integer);
	}

	public static void __writeShort(final OutputStream baos, final short s) throws IOException {
		writeInt2(baos, s);
	}

	public static void __writeUTF(final OutputStream baos, final String str) throws IOException {
		final byte[] bytes = str.getBytes(CharsetUtil.UTF_8);
		__writeInt(baos, bytes.length);
		baos.write(bytes);
	}

	public static void __writeASCII(final OutputStream baos, final String str) throws IOException {
		final byte[] bytes = str.getBytes(CharsetUtil.US_ASCII);
		__writeInt(baos, bytes.length);
		baos.write(bytes);
	}

	public static void __write(final OutputStream baos, final byte[] arr) throws IOException {
		baos.write(arr, 0, arr.length);
	}

	/* heatshrink */

	public static byte[] __heatshrink(final HeatShrinkEncoder hse, final byte[] data) {
		final byte[] output = new byte[32];
		int offset = 0;
		int size = data.length;
		final ByteArrayOutputStream baos = new ByteArrayOutputStream();
		Result res;
		while (true) {
			do {
				if (size <= 0) {
					res = hse.finish();
					break;
				}
				res = hse.sink(data, offset, size);
				size -= res.count;
				offset += res.count;
			} while (res.code != Code.FULL);

			if (res.code == Code.DONE) {
				break;
			} else {
				do {
					res = hse.poll(output);
					if (res.isError())
						throw new RuntimeException("error poll");
					if (res.count > 0) {
						baos.write(output, 0, res.count);
					}
				} while (res.code == MORE);
			}
		}
		return baos.toByteArray();
	}

	public static byte[] __unheatshrink(final HeatShrinkDecoder hse, final byte[] data) {
		final byte[] output = new byte[32];
		int offset = 0;
		int size = data.length;
		final ByteArrayOutputStream baos = new ByteArrayOutputStream();
		Result res;
		while (true) {
			do {
				if (size <= 0) {
					res = hse.finish();
					break;
				}
				res = hse.sink(data, offset, size);
				size -= res.count;
				offset += res.count;
			} while (res.code != Code.FULL);

			if (res.code == Code.DONE) {
				break;
			} else {
				do {
					res = hse.poll(output);
					if (res.isError())
						throw new RuntimeException("error poll");
					if (res.count > 0) {
						baos.write(output, 0, res.count);
					}
				} while (res.code == MORE);
			}
		}
		return baos.toByteArray();
	}

	/* */

	private static long getInt8(byte[] data) {
		return getInt8(data, 0);
	}

	private static long getInt8(byte[] data, int offset) {
		int i = offset;
		int b0 = data[i++] & 0xFF;
		int b1 = data[i++] & 0xFF;
		int b2 = data[i++] & 0xFF;
		int b3 = data[i++] & 0xFF;
		int b4 = data[i++] & 0xFF;
		int b5 = data[i++] & 0xFF;
		int b6 = data[i++] & 0xFF;
		int b7 = data[i++] & 0xFF;
		return getInt(b0, b1, b2, b3, b4, b5, b6, b7);
	}

	private static int getInt4(byte[] data) {
		return getInt4(data, 0);
	}

	private static int getInt4(byte[] data, int offset) {
		int i = offset;
		int b0 = data[i++] & 0xFF;
		int b1 = data[i++] & 0xFF;
		int b2 = data[i++] & 0xFF;
		int b3 = data[i++] & 0xFF;
		return getInt(b0, b1, b2, b3);
	}

	private static int getInt2(byte[] data) {
		return getInt2(data, 0);
	}

	private static int getInt2(byte[] data, int offset) {
		int i = offset;
		int b0 = data[i++] & 0xFF;
		int b1 = data[i++] & 0xFF;
		return getInt(b0, b1);
	}

	private static int getInt(int i0, int i1) {
		return (i1 << 8) + (i0 << 0);
	}

	private static int getInt(int i0, int i1, int i2, int i3) {
		return (i3 << 24) + (i2 << 16) + (i1 << 8) + (i0 << 0);
	}

	private static long getInt(int i0, int i1, int i2, int i3, int i4, int i5, int i6, int i7) {
		// Special check for all 0xff, to avoid overflowing long
		if (i0 == 255 && i1 == 255 && i3 == 255 && i4 == 255 && i5 == 255 && i6 == 255 && i7 == 255)
			return -1l;
		// Otherwise normal convert
		return (long) (i7 << 56) + (long) (i6 << 48) + (long) (i5 << 40) + (long) (i4 << 32) + (i3 << 24) + (i2 << 16) + (i1 << 8) + (i0 << 0);
	}

	/* */

	private static void writeInt8(OutputStream out, long v) throws IOException {
		byte[] b8 = new byte[8];
		putInt8(b8, 0, v);
		out.write(b8);
	}

	private static void putInt8(byte[] data, int offset, long v) {
		int i = offset;
		data[i++] = (byte) ((v >>> 0) & 0xFF);
		data[i++] = (byte) ((v >>> 8) & 0xFF);
		data[i++] = (byte) ((v >>> 16) & 0xFF);
		data[i++] = (byte) ((v >>> 24) & 0xFF);
		data[i++] = (byte) ((v >>> 32) & 0xFF);
		data[i++] = (byte) ((v >>> 40) & 0xFF);
		data[i++] = (byte) ((v >>> 48) & 0xFF);
		data[i++] = (byte) ((v >>> 56) & 0xFF);
	}

	private static void writeInt4(OutputStream out, long v) throws IOException {
		final byte[] b4 = new byte[4];
		putInt4(b4, 0, v);
		out.write(b4);
	}

	private static void putInt4(byte[] data, int offset, long v) {
		int i = offset;
		data[i++] = (byte) ((v >>> 0) & 0xFF);
		data[i++] = (byte) ((v >>> 8) & 0xFF);
		data[i++] = (byte) ((v >>> 16) & 0xFF);
		data[i++] = (byte) ((v >>> 24) & 0xFF);
	}

	private static void writeInt2(OutputStream out, int v) throws IOException {
		final byte[] b2 = new byte[2];
		putInt2(b2, 0, v);
		out.write(b2);
	}

	private static void putInt2(byte[] data, int offset, int v) {
		int i = offset;
		data[i++] = (byte) ((v >>> 0) & 0xFF);
		data[i++] = (byte) ((v >>> 8) & 0xFF);
	}
}
